/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { StackSlot } from './stack_solt';
import { Inst } from './inst';

export function isRepresentableAsInt32(value: number): boolean {
  return (value | 0) === value;
}

export function addIndexed<T>(list: T[], cons: T): T {
  let result = cons;
  list.push(result);
  return result;
}

export let stackAlignmentBytes = 16;

export function roundUpToMultipleOf(amount: number, value: number): number {
  return Math.ceil(value / amount) * amount;
}

export function symbolName(symbol: string): string {
  return symbol;
}

export function lowerSymbolName(symbol: string): string {
  return symbolName(symbol).toLowerCase();
}

function setToString(set: Set<string>): string {
  let result = '';
  for (let value of set) {
    if (result.length > 0) {
      result += ', ';
    }
    result += value;
  }
  return result;
}

export function mergeIntoSet(target: Set<StackSlot>, source: Set<StackSlot>): boolean {
  let didAdded = false;
  for (let value of source) {
    if (target.has(value)) {
      continue;
    }
    target.add(value);
    didAdded = true;
  }
  return didAdded;
}

function nonEmptyRangesOverlap(leftMin: number, leftMax: number, rightMin: number, rightMax: number): boolean {
  if (leftMin >= leftMax) {
    throw new Error('Bad left range');
  }
  if (rightMin >= rightMax) {
    throw new Error('Bad right range');
  }

  if (leftMin <= rightMin && leftMax > rightMin) {
    return true;
  }
  if (rightMin <= leftMin && rightMax > leftMin) {
    return true;
  }
  return false;
}

export function rangesOverlap(leftMin: number, leftMax: number, rightMin: number, rightMax: number): boolean {
  if (leftMin > leftMax) {
    throw new Error('Bad left range');
  }
  if (rightMin > rightMax) {
    throw new Error('Bad right range');
  }

  if (leftMin === leftMax) {
    return false;
  }
  if (rightMin === rightMax) {
    return false;
  }
  return nonEmptyRangesOverlap(leftMin, leftMax, rightMin, rightMax);
}

export function removeAllMatching(array: Inst[], fun: (inst: Inst) => boolean): void {
  let srcIndex = 0;
  let dstIndex = 0;
  while (srcIndex < array.length) {
    let value = array[srcIndex];
    srcIndex += 1;
    if (!fun(value)) {
      array[dstIndex] = value;
      dstIndex += 1;
    }
  }
  array.splice(dstIndex, array.length - dstIndex);
}

export function bubbleSort<T>(array: T[], lessThan: (a: T, b: T) => boolean): void {
  let begin = 0;
  let end = array.length;
  do {
    let changed = false;
    if (end < begin) {
      throw Error('Begin and end are messed up');
    }
    let limit = end - begin;
    let i = limit;
    while (i > 1) {
      i -= 1;
      bubble(array, begin + i, begin + i - 1, lessThan, changed);
    }
    if (!changed) {
      return;
    }
    // After one run, the first element in the list is guaranteed to be the smallest.
    begin += 1;
    // Now go in the other direction. This eliminates most sorting pathologies.
    changed = false;
    if (end < begin) {
      throw Error('Begin and end are messed up');
    }
    limit = end - begin;
    i = 0;
    while (i < limit) {
      bubble(array, begin + i, begin + i - 1, lessThan, changed);
      i += 1;
    }
    if (!changed) {
      return;
    }
    // Now the last element is guaranteed to be the largest.
    end -= 1;
  } while (true);
}
function bubble<T>(array: T[], i: number, j: number, lessThan: (a: T, b: T) => boolean, changed: boolean): void {
  if (lessThan(array[i], array[j])) {
    swap(array, i, j);
    changed = true;
  }
}

function swap<T>(array: T[], i: number, j: number): void {
  let tmp = array[i];
  array[i] = array[j];
  array[j] = tmp;
}
